#include "dh.h"
#include <string.h>
#include "map2.h"
#include "base.h"
#include "log.h"

typedef struct dh_global_st
{
    char config_path[FILENAME_MAX]; // 配置文件名
    long long configs_fltime;       // 配置文件最后更新时间
    map_t configs;                  // key: char * val: char *
    map_t datas;                    // 全局数据字典
    FILE *log_fp;                   // 日志文件fp
} dh_global_t;

static dh_global_t dhg = {
    .config_path = {0},
    .configs = {0},
    .configs_fltime = -1,
    .datas = {0},
    .log_fp = NULL,
};

static int __map_str_key_equal(const void *key1, const void *key2)
{
    char *k1 = (char *)key1;
    char *k2 = (char *)key2;
    if (strcmp(k1, k2) == 0)
        return 1;
    return 0;
}

static void __map_kv_deinit_before(const void *key, const void *value)
{
    global_mpool_free(key);
    global_mpool_free(value);
}

static size_t __global_mpool_page_size = 4096;

/// @brief 系统初始化
/// @param config_path 配置文件路径
/// @return 0: 成功 -1: 全局内存池创建失败(内存不足) -2：查看文件大小失败 -3：读取配置文件失败
int system_init(const char *config_path)
{
    int ret = 0;
    if (config_path == NULL || dhg.config_path[0] != '\0')
    {
        ret = -1;
        goto fail0;
    }
    // 内存池
    ret = global_mpool_init(__global_mpool_page_size);
    if (ret != 0)
        goto fail0;

    // 记录配置文件名
    size_t config_path_len = strlen(config_path);
    config_path_len = config_path_len > FILENAME_MAX ? FILENAME_MAX : config_path_len;
    strncpy(dhg.config_path, config_path, config_path_len);
    // 查看文件大小
    size_t file_size = fsize(dhg.config_path);
    if (file_size == -1)
    {
        ret = -2;
        goto fail0;
    }

    // map内存控制函数设置
    map_memctl_set(global_mpool_malloc, global_mpool_free);
    // 配置map 初始化
    map_init(&(dhg.configs), map_murmur_hash, __map_str_key_equal, __map_kv_deinit_before);
    // 数据map初始化
    map_init(&(dhg.datas), map_murmur_hash, __map_str_key_equal, __map_kv_deinit_before);

    // 同步文件数据
    if (config_sync() != 0)
        ret = -3;
    // 日志初始化
    log_sys_init(config_get("log_file"), config_get("log_lowlevel"));

fail0:
    return ret;
}

/// @brief 处理ini文件每一行内容 \n作为结束符号 同步更新到全局变量中dhg.configs字典中
/// @param data
static void __ini_configs_sync(const char *data)
{
    map_t *map = &(dhg.configs);

    char *_key = data;       // 临时存储键
    size_t _key_len = 0;     // 用于记录_key 往后移动多长为一段数据
    char *_val = NULL;       // 临时存储键
    size_t _val_len = 0;     // 用于记录_val 往后移动多长为一段数据
    char *_section = NULL;   // 当前节名称
    size_t _section_len = 0; // 用于记录_section 往后移动多长为一段数据

    while (*data != '\0')
    {
        if (*data == ';') // 注释
        {
            while (*data != '\0')
            {
                data++;
                if (*data == '\n')
                    break;
            } // 循环结束 *data 此时为 '\n'
        }
        else if (*data == '[')
        {
            data++; // 跳过 '['
            _section = data;
            _section_len = 0;
            while (*data != '\0')
            {
                if (*data == ']')
                    break;
                _section_len++;
                data++;
            } // 循环结束 *data 此时为 ']'
        }
        else if (*data == '=') // 转value
        {
            data++; // 跳过 '='
            _val = data;
            _val_len = 0;
            while (*data != '\0')
            {
                if (*data == '\n' || *data == ';')
                    break;
                _val_len++;
                data++;
            } // 循环结束 *data 此时为 '\n'

            size_t _real_key_len = _section_len == 0 ? _key_len + 1 : _section_len + 1 + _key_len + 1;
            char *key = global_mpool_malloc(_real_key_len);
            if (key != NULL)
            {
                memset(key, 0, _real_key_len);
                size_t _key_offset = 0;
                // 节数据拷贝
                if (_section_len != 0)
                {
                    strncpy(key, _section, _section_len);
                    strncpy(key + _section_len, "_", 1);
                    _key_offset = _section_len + 1;
                }
                strncpy(key + _key_offset, _key, _key_len);
            }
            char *val = global_mpool_malloc(_val_len + 1);
            if (val != NULL)
            {
                memset(val, 0, _val_len);
                strncpy(val, _val, _val_len);
            }
            // 此处-1是为了去掉 \0 其实可以用 strlen(key)
            if (map_put(map, key, _real_key_len - 1, val) == 1) // 已存在该key
                map_upt(map, key, _real_key_len - 1, val);
        }
        else // key
        {
            if (*data != ']' && *data != '\n')
            {
                _key = data;
                _key_len = 0;
                while (*data != '\0')
                {
                    _key_len++;
                    if (*(data + 1) == '=' || *(data + 1) == ';')
                        break;
                    data++;
                } // 循环结束 *data 此时为 '=' 前一个字符
            }
        }

        data++;
    }
}

/// @brief config文件同步
/// @param file
/// @return 0:成功 -1:未初始化 -2:fltime/fsize失败 -3:无法打开
int config_sync()
{
    if (dhg.config_path[0] == '\0')
        return -1;
    long long ftime = fltime(dhg.config_path);
    if (ftime == -1)
        return -2;
    if (ftime == dhg.configs_fltime)
        return 0;

    size_t file_size = fsize(dhg.config_path);
    if (file_size == -1)
        return -2;

    FILE *txt = fopen(dhg.config_path, "rb");
    if (txt == NULL)
        return -3;
    char *data = global_mpool_malloc(file_size + 1);
    if (data == NULL)
        goto fail1;

    size_t n = fread((void *)data, sizeof(char), file_size, txt);
    if (n != file_size)
        goto fail1;
    if (ferror(txt))
        goto fail1;
    __ini_configs_sync(data);
    dhg.configs_fltime = ftime;
fail1:
    if (data != NULL)
        global_mpool_free(data);
    if (txt != NULL)
        fclose(txt);
fail0:
    return 0;
}

/// @brief 获取配置文件中键值对
/// @param key 键
/// @return 值
char *config_get(const char *key)
{
    int ret = config_sync();
    if (ret == -1)
        log_error("未初始化完成时 尝试获取 %s 配置", key);
    else if (ret == -2)
        log2_warning(DH_LOG, "尝试获取 %s 配置 同步文件是获取文件fltime/fsize失败 读取内存数据", key);
    else if (ret == -3)
        log2_warning(DH_LOG, "尝试获取 %s 配置 无法打开文件 读取内存数据", key);
    return (char *)map_get(&(dhg.configs), (void *)key, strlen(key));
}

static void __config_foreach_print(const void *key, void *value, void *userdata)
{
    char *k = (char *)key;
    char *v = (char *)value;
    printf("configs[%s]:%s\n", k, v);
}

/// @brief 打印所有的配置项目
void config_foreach_print()
{
    printf("=========configs===========\n");
    map_foreach(&(dhg.configs), __config_foreach_print, NULL);
    printf("=========configs===========\n");
}

static void __log_sys_deinit()
{
    if (dhg.log_fp == NULL)
        return;
    fclose(dhg.log_fp);
    dhg.log_fp = NULL;
}

/// @brief 初始化日志模块
/// @param filename
/// @param lowlevel
void log_sys_init(const char *filename, const char *lowlevel)
{
    if (filename == NULL)
        return;
    dhg.log_fp = fopen(filename, "a+");
    if (dhg.log_fp == NULL)
        dhg.log_fp = fopen(filename, "w");

    int low_level = log_str_level(lowlevel);
    log_add_fp2(dhg.log_fp, DH_LOG, low_level);
    atexit(__log_sys_deinit);
}